﻿using UnityEngine;
using System.Collections;
using System.Collections.Generic;

public class AStar : MonoBehaviour 
{

		Vector2[] _movements;
		
		private Node[,] allNodes;
		
		private Node origin;
		
		private int row;
		private int column;
		
		private bool hasPath;
		
		private List<Node> path = new List<Node>();

		public Grid grid;

		private nodeState lastGoalState;
		private nodeState lastOriginState;

		void Awake()
		{
		grid = Camera.main.GetComponent<Grid>();
			_movements = new Vector2[]
			{
				new Vector2(0,-1),
				new Vector2(1, 0),
				new Vector2(0, 1),
				new Vector2(-1, 0)
			};
		}

		
		public void ReloadPathfindingConfigurations()
		{
			Recalcule();
			hasPath = false;
			path.Clear();
		}
		
		public void Recalcule()
		{
			foreach (Vector2 point in AllNodes())
			{
				int x = (int)point.x;
				int y = (int)point.y;
				allNodes[x, y].distanceSteps = 10000;
				allNodes[x, y].hasPath = false;
			}
		}
		
		public List<Node> Search(Node origin, Node goal)
		{
			ReloadPathfindingConfigurations();

			this.origin = origin;
			this.allNodes = grid.nodes;
			this.row = grid.Rows;
			this.column = grid.Columns;
			
			path.Add(origin);
			
			lastGoalState = goal.currentState;
			lastOriginState = origin.currentState;

			goal.currentState = nodeState.GOAL;
			origin.currentState = nodeState.ORIGIN;
			
			PathMapping();
			
			GeneratePath();
			
			return path;
		}
		
		public void PathMapping()
		{
			Vector2 startingPoint = FindNode(nodeState.GOAL);
			
			int startingX = (int)startingPoint.x;
			int startingY = (int)startingPoint.y;
			
			if (startingX == -1 || startingY == -1)
			{
				return;
			}
			
			allNodes[startingX, startingY].distanceSteps = 0;
			
			while (true)
			{
				bool running = false;
				
				foreach (Vector2 currentPoint in AllNodes())
				{
					int x = (int)currentPoint.x;
					int y = (int)currentPoint.y;
					
					if (NodeStatus(x, y))
					{
						int currentDistance = allNodes[x, y].distanceSteps;
						
						foreach (Vector2 moverPonto in MovementAvailable(x, y))
						{
							int newX = (int)moverPonto.x;
							int newY = (int)moverPonto.y;
							
							int newDistanceSteps = currentDistance + 1;
							
							if (allNodes[newX, newY].distanceSteps > newDistanceSteps)
							{
								allNodes[newX, newY].distanceSteps = newDistanceSteps;
								running = true;
							}
						}
					}
				}
				
				if (!running)
				{
					break;
				}
			}
		}
		
		public void GeneratePath()
		{
			Vector2 startingPoint = FindNode(nodeState.ORIGIN);
			
			int pointX = (int)startingPoint.x;
			int pointY = (int)startingPoint.y;
			
			if (pointX == -1 && pointY == -1)
			{
				return;
			}
			
			while (true)
			{
				Vector2 lowestPoint = Vector2.zero;
				
				int lowestDistance = 10000;
				
				foreach (Vector2 movePoint in MovementAvailable(pointX, pointY))
				{
					int originDistanceSteps = allNodes[(int)movePoint.x, (int)movePoint.y].distanceSteps;
					
					if (originDistanceSteps < lowestDistance)
					{
						lowestDistance = originDistanceSteps;
						lowestPoint.x = movePoint.x;
						lowestPoint.y = movePoint.y;
					}
				}
				
				if (lowestDistance != 10000)
				{
					int x = (int)lowestPoint.x;
					int y = (int)lowestPoint.y;
					
					allNodes[x, y].hasPath = true;
					
					path.Add(allNodes[x, y]);
					
					
					pointX = (int)lowestPoint.x;
					pointY = (int)lowestPoint.y;
				}
				else
				{
					return;
				}
				
				if (allNodes[pointX, pointY].currentState == nodeState.GOAL)
				{
					hasPath = true;
					
					path.Add(allNodes[pointX, pointY]);
					
					origin.hasPath = true;

					origin.currentState = lastOriginState;
					allNodes[pointX, pointY].currentState = lastGoalState;
					
					break;
				}
			}
		}
		
		private Vector2 FindNode(nodeState contentIn)
		{
			foreach (Vector2 point in AllNodes())
			{
				if (allNodes[(int)point.x, (int)point.y].currentState == contentIn)
				{
					return new Vector2(point.x, point.y);
				}
			}
			return new Vector2(-1, -1);
		}
		
		private bool NodeStatus(int x, int y)
		{
			switch (allNodes[x, y].currentState)
			{
			case nodeState.EMPTY:
				return true;
			case nodeState.GOAL:
				return true;
			case nodeState.ORIGIN:
				return true;
			case nodeState.WALL:
			default:
				return false;
			}
		}
		
		private bool AvaliableCoordinates(int x, int y)
		{
			if (x < 0)
			{
				return false;
			}
			if (y < 0)
			{
				return false;
			}
			if (x > this.column - 1)
			{
				return false;
			}
			if (y > this.row - 1)
			{
				return false;
			}
			return true;
		}
		
		private IEnumerable<Vector2> AllNodes()
		{
			
			for (int x = 0; x < this.column; x++)
			{
				for (int y = 0; y < this.row; y++)
				{
					yield return new Vector2(x, y);
				}
			}
		}
		
		private IEnumerable<Vector2> MovementAvailable(int x, int y)
		{
			foreach (Vector2 movePoint in _movements)
			{
				int newX = x + (int)movePoint.x;
				int newY = y + (int)movePoint.y;
				
				if (AvaliableCoordinates(newX, newY) && NodeStatus(newX, newY))
				{
					yield return new Vector2(newX, newY);
				}
			}
		}
		
		public bool hPath()
		{
			return hasPath;
		}
}
